<?php
namespace Pinenut\Model;

/**
 * Базовая модель, реализует функции вложенной модели
 * @author Kosh
 *
 */
class Embedded {

	const ID        = 1;
	const INT       = 2;
	const STRING    = 3;
	const DATE      = 4;
	
	
	protected $_parent; // родитель
	protected $_field; // поле у базовой модели
	
	protected $_model = null; //базовая модель
	protected $_fullFieldName = null; // полное имя поля

	static function fields() {
		return array();
	}
	
	/**
	 * Составляем список полей модели, включая вложенные, с соответствующим именованием полей 
	 * @return Ambigous <multitype:, unknown>
	 */
	static function getFieldTypes() {
		$fields = static::fields();
		$types = array();
		foreach ($fields as $key=>$value) {
			if (is_int($value)) {
				$types[$key] = $value;		
			} elseif ($value instanceof \Pinenut\Model\Embedded) {
				if ($value instanceof \Pinenut\Model\ReferenceSet) 
					$value = $value->getEmbedded();
					
				$subTypes =  $value::getFieldTypes();
				foreach ($subTypes as $k=>$v) 
					$types[$key.'.'.$k] = $v;
			}
		}
		return $types;
	}
	
	/**
	 * Берём массив полей и иницируем вложенные в них объекты, 
	 * а именно: прописываем имя класса и имя поля, к которому относится вложенный документ
	 * это необходимо, как минимум, для корректной работы моделей-ссылок, чтобы они делали 
	 * адекватные запросы для поддержания своей целостности
	 * 
	 * @return multitype:
	 */
	function initializedFields() {
		$fields = static::fields();
		
		foreach ($fields as $fieldName => $fieldType) {
			if (is_array($fieldType)) 
				$fieldType = array_shift($fieldType);
				
			if ($fieldType instanceof Embedded) {
				$fieldType->setParent($this);
				$fieldType->setField($fieldName); // TODO: сделать поле с учетом вложенности
			}
		}
		
		return $fields;
	}
	
	function setParent(Embedded $parent) {
		$this->_parent = $parent;
	}
	
	function setField($field) {
		$this->_field = $field;
	}
	
	/**
	 * Возвращаем значение поля (пути)
	 * @param string $field
	 */
	function getValue($field) {
		$path = explode('.', $field, 2);
		$current = $path[0];
		$more = isset($path[1])?$path[1]:null;
		if (property_exists($this, $current)) {
			if (!empty($more) 
					&& is_object($this->$current) 
					&& $this->$current instanceof \Pinenut\Model\Embedded) {
				return $this->$current->getValue($more);
			} else {
				return $this->$current;
			}
		} else return null;
	}

//	public function __constructor(array $data = array(), $fromStore = false) {
//		$this->fromArray($data, $fromStore);
//		return $this;
//	}

	public function populate(array $data = array(), $fromStore = false) {
		$fields = $this->initializedFields();
//print_r($fields); print_r($data);
//		foreach ($data as $key => $value) {
//			if (isset($fields[$key])) {
		foreach ($fields as $key=>$field) {
			if (isset($data[$key])) 
				$value = $data[$key]; 
			else 
				$value = null;
				//$field = $fields[$key];
				$arrayOf = false;

				if (is_array($field)) {

					$arrayOf = true;
					if (!is_array($value)) $value = array($value);
					$field = array_shift($field);

					if (is_object($field)) {
						foreach ($value as $k=>&$v) {
							if (null !== $v) {
								$tmp = clone $field;
								$tmp->populate($v, $fromStore);
								$v = $tmp;
							}
						}
					}
					
				} else {
					if (is_object($field)) {
						//if (!is_array($value)) {
							$value = (array)$value;
						//}
						$tmp = clone $field;
						$tmp->populate($value, $fromStore);
						$value = $tmp;
						
					}

				}
				$this->$key = $value;
			//}
		}

		return $this;
	}

	public function toArray($forStore = false) {
		$fields = $this->initializedFields();
		$model = $this->getModel();
		//print_r($fields);exit;
		$data = array();
		foreach ($fields as $key=>$field) {
			$arrayOf = false;
			$value = $this->$key;
			if (is_array($field)) {
				$arrayOf = true;
				$field = array_shift($field);
				if (is_object($field)) {
					if (is_array($value)) {
						foreach ($value as $k=>&$v) {
							$v = $v->toArray($forStore);
						}
					} else {
						$value = array();
					}
				}
			} else {
				if (is_object($field)) { 
					if (is_object($value)) {
						$value = $value->toArray($forStore);
					} else {
						if ($forStore) 
							$value = array(); // для пустого непроинициированного объекта- просто массив 
					}
				} else {
					// если данные собираем для сохранения, 
					// то простое значение поля приводим к указанному типу
					if ($forStore) $value = $model::typeCast($value, $field);
				}
			}

			$data[$key] = $value;
		}
		return $data;
	}
	
	
	static function typeCast($value, $type) {
		switch ($type) {
			case static::STRING:
				$value = (string)$value;
				break;
			case static::INT:
				$value = null !== $value ? intval($value) : null;
				break;
		}
		
		return $value;
	}
	
	static function getClassName() {
		return get_called_class();
	}
	
	/**
	 * Возвращаем базовый объект модели
	 * @return \Pinenut\Model
	 */
	
	public function getModel() {
		if (null === $this->_model) {
			$this->_model = $this->_parent->getModel();
		}
		return $this->_model;
	}
	
	public function getFullFieldName() {
		if (null === $this->_fullFieldName) {
			$this->_fullFieldName = $this->_parent->getFullFieldName();
			if (!empty($this->_fullFieldName)) $this->_fullFieldName .= '.';
			$this->_fullFieldName .= $this->_field;
		}
		return $this->_fullFieldName;
	}
	
	/**
	 * Так же как файндер - делаем апдейтер, чтобы он при его вызове относительно вложенного документа, 
	 * уже делал поправку и обновлял поля в этом вложенном документе
	 * 
	 * 
	 * @return \Pinenut\Model\Updater
	 */

	function modify() {
		return $this->getModel()
				->modify()
			  	->setDefaultField($this->getFullFieldName());
	}
	
	
	static function _getFromMultiArrayByPath($array, $path) {
		$chunks = explode('.', $path);
		switch (count($chunks)) {
			case 1: if (isset($array[$chunks[0]])) 
				return $array[$chunks[0]]; else break;
			case 2: if (isset($array[$chunks[0]][$chunks[1]])) 
				return $array[$chunks[0]][$chunks[1]]; else break;
			case 3: if (isset($array[$chunks[0]][$chunks[1]][$chunks[2]])) 
				return $array[$chunks[0]][$chunks[1]][$chunks[2]]; else break;
			case 4: if (isset($array[$chunks[0]][$chunks[1]][$chunks[2]][$chunks[3]])) 
				return $array[$chunks[0]][$chunks[1]][$chunks[2]][$chunks[3]]; else break;
			case 5: if (isset($array[$chunks[0]][$chunks[1]][$chunks[2]][$chunks[3]][$chunks[4]])) 
				return $array[$chunks[0]][$chunks[1]][$chunks[2]][$chunks[3]][$chunks[4]]; else break;
		}
		
		return null;
	}
	
}